/*
 * 作为输入参数
 * 当以闭包作为输入参数时，必须指出闭包的完整类型。
 * 它是通过使用以下`trait`中的一种来指定的。受限制程度按以下顺序递减
 * - `Fn`：表示捕获方式为通过引用 `&T`的闭包
 * - `FnMut`：表示捕获方式为通过可变引用 `&mut T`的闭包
 * - `FnOnce`：表示捕获方式为通过值`T`的闭包
 * 顺序之所以这样，是因为`&T`只是获取了不可变的引用，`&mut T`则可以改变变量，
 * `T`则是拿到了变量的所有权而非借用
 *
 * 编译器在满足使用需求的前提下尽量以限制最多的方式捕获。但最终是根据所捕获变量在
 * 闭包里的使用情况决定捕获方式。
 *
 * 如果参数的类型说明是`Fn`、那么不允许该闭包通过 `&mut T` 或 `T`捕获变量
 *
 */
// 该函数将闭包作为参数并调用它
fn apply<F>(f: F) where
    // 闭包没有输入值和返回值
    F: FnOnce() {

    f();
}

// 输入闭包，返回一个`i32`整型的函数
fn apply_to_3<F>(f: F) -> i32 where
    // 闭包处理一个`i32`整型并返回一个`i32`整型
    F: Fn(i32) -> i32 {

    f(3)
}

fn main() {
    use std::mem;

    let greeting = "hello";
    // 不可复制的类型
    // `to_owned` 从借用的数据创建有所有权的数据
    let mut farewell = "goodbye".to_owned();

    // 捕获2个变量：通过引用捕获`greeting`，通过值捕获`farewell`
    let diary = || {
        // `greeting` 通过引用捕获，故需要闭包是`Fn`
        println!("I said {}", greeting);

        // 下文改变了 `farewell`，因而要求闭包通过可变引用来捕获它
        // 现在需要`FnMut`
        farewell.push_str("!!");
        println!("Then I screamed {}", farewell);

        // 手动调用drop 又要求闭包通过值获取 `farewell`
        // 现在需要`FnOnce`
        mem::drop(farewell);
    };

    // 以闭包作为参数，调用函数 `apply`
    apply(diary);

    // 闭包`double` 满足 `apple_to_3`的trait 约束
    let double = |x| 2 * x;

    println!("3 doubled: {}", apply_to_3(double));
}